<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->

<link rel="import" href="../utils/boot.html">
<link rel="import" href="../utils/telemetry.html">
<link rel="import" href="../utils/mixin.html">
<link rel="import" href="../mixins/properties-changed.html">

<script>
(function() {
  'use strict';

  /**
   * Creates a copy of `props` with each property normalized such that
   * upgraded it is an object with at least a type property { type: Type}.
   *
   * @param {Object} props Properties to normalize
   * @return {Object} Copy of input `props` with normalized properties that
   * are in the form {type: Type}
   * @private
   */
  function normalizeProperties(props) {
    const output = {};
    for (let p in props) {
      const o = props[p];
      output[p] = (typeof o === 'function') ? {type: o} : o;
    }
    return output;
  }

  /**
   * Mixin that provides a minimal starting point to using the PropertiesChanged
   * mixin by providing a mechanism to declare properties in a static
   * getter (e.g. static get properties() { return { foo: String } }). Changes
   * are reported via the `_propertiesChanged` method.
   *
   * This mixin provides no specific support for rendering. Users are expected
   * to create a ShadowRoot and put content into it and update it in whatever
   * way makes sense. This can be done in reaction to properties changing by
   * implementing `_propertiesChanged`.
   *
   * @mixinFunction
   * @polymer
   * @appliesMixin Polymer.PropertiesChanged
   * @memberof Polymer
   * @summary Mixin that provides a minimal starting point for using
   * the PropertiesChanged mixin by providing a declarative `properties` object.
   */
   Polymer.PropertiesMixin = Polymer.dedupingMixin(superClass => {

    /**
     * @constructor
     * @extends {superClass}
     * @implements {Polymer_PropertiesChanged}
     * @private
     */
    const base = Polymer.PropertiesChanged(superClass);

    /**
     * Returns the super class constructor for the given class, if it is an
     * instance of the PropertiesMixin.
     *
     * @param {!PropertiesMixinConstructor} constructor PropertiesMixin constructor
     * @return {PropertiesMixinConstructor} Super class constructor
     */
    function superPropertiesClass(constructor) {
      const superCtor = Object.getPrototypeOf(constructor);

      // Note, the `PropertiesMixin` class below only refers to the class
      // generated by this call to the mixin; the instanceof test only works
      // because the mixin is deduped and guaranteed only to apply once, hence
      // all constructors in a proto chain will see the same `PropertiesMixin`
      return (superCtor.prototype instanceof PropertiesMixin) ?
        /** @type {PropertiesMixinConstructor} */ (superCtor) : null;
    }

    /**
     * Returns a memoized version of the `properties` object for the
     * given class. Properties not in object format are converted to at
     * least {type}.
     *
     * @param {PropertiesMixinConstructor} constructor PropertiesMixin constructor
     * @return {Object} Memoized properties object
     */
    function ownProperties(constructor) {
      if (!constructor.hasOwnProperty(JSCompiler_renameProperty('__ownProperties', constructor))) {
        let props = null;

        if (constructor.hasOwnProperty(JSCompiler_renameProperty('properties', constructor))) {
          const properties = constructor.properties;
          
          if (properties) {
            props = normalizeProperties(properties);
          }
        }

        constructor.__ownProperties = props;
      }
      return constructor.__ownProperties;
    }

    /**
     * @polymer
     * @mixinClass
     * @extends {base}
     * @implements {Polymer_PropertiesMixin}
     * @unrestricted
     */
    class PropertiesMixin extends base {

      /**
       * Implements standard custom elements getter to observes the attributes
       * listed in `properties`.
       * @suppress {missingProperties} Interfaces in closure do not inherit statics, but classes do
       */
      static get observedAttributes() {
        if (!this.hasOwnProperty('__observedAttributes')) {
          Polymer.telemetry.register(this.prototype);
          const props = this._properties;
          this.__observedAttributes = props ? Object.keys(props).map(p => this.attributeNameForProperty(p)) : [];
        }
        return this.__observedAttributes;
      }

      /**
       * Finalizes an element definition, including ensuring any super classes
       * are also finalized. This includes ensuring property
       * accessors exist on the element prototype. This method calls
       * `_finalizeClass` to finalize each constructor in the prototype chain.
       * @return {void}
       */
      static finalize() {
        if (!this.hasOwnProperty(JSCompiler_renameProperty('__finalized', this))) {
          const superCtor = superPropertiesClass(/** @type {PropertiesMixinConstructor} */(this));
          if (superCtor) {
            superCtor.finalize();
          }
          this.__finalized = true;
          this._finalizeClass();
        }
      }

      /**
       * Finalize an element class. This includes ensuring property
       * accessors exist on the element prototype. This method is called by
       * `finalize` and finalizes the class constructor.
       *
       * @protected
       */
      static _finalizeClass() {
        const props = ownProperties(/** @type {PropertiesMixinConstructor} */(this));
        if (props) {
          this.createProperties(props);
        }
      }

      /**
       * Returns a memoized version of all properties, including those inherited
       * from super classes. Properties not in object format are converted to
       * at least {type}.
       *
       * @return {Object} Object containing properties for this class
       * @protected
       */
      static get _properties() {
        if (!this.hasOwnProperty(
          JSCompiler_renameProperty('__properties', this))) {
          const superCtor = superPropertiesClass(/** @type {PropertiesMixinConstructor} */(this));
          this.__properties = Object.assign({},
            superCtor && superCtor._properties,
            ownProperties(/** @type {PropertiesMixinConstructor} */(this)));
        }
        return this.__properties;
      }

      /**
       * Overrides `PropertiesChanged` method to return type specified in the
       * static `properties` object for the given property.
       * @param {string} name Name of property
       * @return {*} Type to which to deserialize attribute
       *
       * @protected
       */
      static typeForProperty(name) {
        const info = this._properties[name];
        return info && info.type;
      }

      /**
       * Overrides `PropertiesChanged` method and adds a call to
       * `finalize` which lazily configures the element's property accessors.
       * @override
       * @return {void}
       */
      _initializeProperties() {
        Polymer.telemetry.instanceCount++;
        this.constructor.finalize();
        super._initializeProperties();
      }

      /**
       * Called when the element is added to a document.
       * Calls `_enableProperties` to turn on property system from
       * `PropertiesChanged`.
       * @suppress {missingProperties} Super may or may not implement the callback
       * @return {void}
       */
      connectedCallback() {
        if (super.connectedCallback) {
          super.connectedCallback();
        }
        this._enableProperties();
      }

      /**
       * Called when the element is removed from a document
       * @suppress {missingProperties} Super may or may not implement the callback
       * @return {void}
       */
      disconnectedCallback() {
        if (super.disconnectedCallback) {
          super.disconnectedCallback();
        }
      }

    }

    return PropertiesMixin;

  });

})();

</script>
